package watcher

import (
	"sync"
	"time"
	"bytes"
	"fmt"
	"github.com/shirou/gopsutil/host"
	"github.com/shirou/gopsutil/load"
	"github.com/shirou/gopsutil/mem"
	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/net"
	"github.com/shirou/gopsutil/disk"
	"io/ioutil"
)

type MetrucExportor struct {
	mu           sync.RWMutex
	interval     int64
	lastScanTime int64
	value        map[string]map[string]float64 //metricname  yy="yy",xx="xx"
	metrics      []string
	//
	netLastValues  map[string]uint64
	cpuLastValues  map[string]float64
	diskLastValues map[string]uint64
}

func NewMetrucExportor(interval int64, metrics []string) *MetrucExportor {
	return &MetrucExportor{
		interval: interval,
		metrics:  metrics,
	}
}

func (me *MetrucExportor) Export(tags map[string]string) (string, error) {
	if err := me.refresh(); err != nil {
		return "", err
	}
	me.mu.RLock()
	defer me.mu.RUnlock()
	var buf bytes.Buffer
	for k, v := range tags {
		buf.WriteString(fmt.Sprintf(`%s="%s, "`, k, v))
	}
	tagstr := buf.String()
	buf.Reset()
	for metricName, m := range me.value {
		for detail, v := range m {
			if metricName == "uptime" {
				buf.WriteString(fmt.Sprintf(`# TYPE %s counter\n\n`, metricName))
			} else {
				buf.WriteString(fmt.Sprintf(`# TYPE %s gauge\n\n`, metricName))
			}
			buf.WriteString(fmt.Sprintf(`%s{%s,%s} %g %d\n`, metricName, tagstr, detail, v, me.lastScanTime*100))
		}
	}
	return buf.String(), nil
}

func (me *MetrucExportor) refresh() error {
	me.mu.Lock()
	defer me.mu.Unlock()
	if me.lastScanTime != 0 && time.Now().Unix()-me.lastScanTime < me.interval {
		return nil
	}
	me.value = make(map[string]map[string]float64)
	me.lastScanTime = time.Now().Unix()
	for _, m := range me.metrics {
		var err error
		switch m {
		case "UpTimeAndProcs":
			err = me.getUpTimeAndProcs()
		case "Load":
			err = me.getLoad()
		case "Misc":
			err = me.getMisc()
		case "VirtualMemory":
			err = me.getVirtualMemory()
		case "SwapMemory":
			err = me.getSwapMemory()
		case "CPU":
			err = me.getCPU()
		case "NetIOCounters":
			err = me.getNetIOCounters()
		case "TcpStatus":
			err = me.getTcpStatus()
		case "DiskUsage":
			err = me.getDiskUsage()
		case "DiskIOCounters":
			err = me.getDiskIOCounters()
		}
		if err != nil {
			return err
		}
	}
	return nil
}

func (me *MetrucExportor) getUpTimeAndProcs() error {
	info, err := host.Info()
	if err != nil {
		return err
	}
	if _, ok := me.value["host"]; !ok {
		me.value["host"] = make(map[string]float64)
	}
	me.value["host"][`m="uptime"`] = float64(info.Uptime)
	me.value["host"][`m="procs"`] = float64(info.Procs)
	return nil
}

func (me *MetrucExportor) getLoad() error {
	la, err := load.Avg()
	if err != nil {
		return err
	}
	if _, ok := me.value["load"]; !ok {
		me.value["load"] = make(map[string]float64)
	}
	me.value["host"][`m="load1"`] = float64(la.Load1)
	me.value["host"][`m="load5"`] = float64(la.Load5)
	me.value["host"][`m="load15"`] = float64(la.Load15)
	return nil
}

func (me *MetrucExportor) getMisc() error {
	lm, err := load.Misc()
	if err != nil {
		return err
	}
	if _, ok := me.value["misc"]; !ok {
		me.value["misc"] = make(map[string]float64)
	}
	me.value["misc"][`m="procsRunning"`] = float64(lm.ProcsRunning)
	me.value["misc"][`m="procsBlocked"`] = float64(lm.ProcsBlocked)
	me.value["misc"][`m="ctxt"`] = float64(lm.Ctxt)
	return nil
}

func (me *MetrucExportor) getVirtualMemory() error {
	v, err := mem.VirtualMemory()
	if err != nil {
		return err
	}
	if _, ok := me.value["mem"]; !ok {
		me.value["mem"] = make(map[string]float64)
	}
	if v.Total != 0 {
		me.value["mem"][`m="used"`] = float64(v.Used) / float64(v.Total)
		me.value["mem"][`m="available"`] = float64(v.Available) / float64(v.Total)
		me.value["mem"][`m="free"`] = float64(v.Free) / float64(v.Total)
		me.value["mem"][`m="active"`] = float64(v.Active) / float64(v.Total)
		me.value["mem"][`m="inactive"`] = float64(v.Inactive) / float64(v.Total)
		me.value["mem"][`m="buffers"`] = float64(v.Buffers) / float64(v.Total)
		me.value["mem"][`m="cached"`] = float64(v.Cached) / float64(v.Total)
		me.value["mem"][`m="shared"`] = float64(v.Shared) / float64(v.Total)
	}
	return nil
}

func (me *MetrucExportor) getSwapMemory() error {
	v, err := mem.SwapMemory()
	if err != nil {
		return err
	}
	if _, ok := me.value["mem"]; !ok {
		me.value["mem"] = make(map[string]float64)
	}
	if v.Total != 0 {
		me.value["mem"][`m="swap_used"`] = float64(v.Used) / float64(v.Total)
		me.value["mem"][`m="swap_free"`] = float64(v.Free) / float64(v.Total)
		me.value["mem"][`m="swap_sin"`] = float64(v.Sin) / float64(v.Total)
	}
	return nil
}

func CounterMinus(last, now map[string]uint64) map[string]float64 {
	ret := make(map[string]float64)
	if last == nil {
		return ret
	}
	for k, v := range now {
		lastV, ok := last[k]
		if ok {
			ret[k] = float64(v - lastV)
		}
	}
	return ret
}

func (me *MetrucExportor) getNetIOCounters() error {
	ncs, err := net.IOCounters(false)
	if err != nil {
		return err
	}
	netNowValues := make(map[string]uint64)
	for _, nc := range ncs {
		name := nc.Name
		netNowValues[name+"_bytes_sent"] = nc.BytesSent
		netNowValues[name+"_bytes_recv"] = nc.BytesRecv
		netNowValues[name+"_packets_sent"] = nc.PacketsSent
		netNowValues[name+"_packets_recv"] = nc.PacketsRecv
	}
	if _, ok := me.value["net_iorate"]; !ok {
		me.value["net_iorate"] = make(map[string]float64)
	}
	for k, v := range CounterMinus(me.netLastValues, netNowValues) {
		me.value["net_iorate"][fmt.Sprintf(`m="%s"`, k)] = v
	}
	me.netLastValues = netNowValues
	return nil
}

func (me *MetrucExportor) getDiskIOCounters() error {
	diskNowValues := make(map[string]uint64)
	dcs, err := disk.IOCounters()
	if err != nil {
		return err
	}
	for name, dc := range dcs {
		diskNowValues[fmt.Sprintf(`path="%s",m="%s"`, name, "read_count")] = dc.ReadCount
		diskNowValues[fmt.Sprintf(`path="%s",m="%s"`, name, "write_count")] = dc.WriteCount
		diskNowValues[fmt.Sprintf(`path="%s",m="%s"`, name, "read_bytes")] = dc.ReadBytes
		diskNowValues[fmt.Sprintf(`path="%s",m="%s"`, name, "write_bytes")] = dc.WriteBytes
		diskNowValues[fmt.Sprintf(`path="%s",m="%s"`, name, "iotime")] = dc.IoTime
		diskNowValues[fmt.Sprintf(`path="%s",m="%s"`, name, "weightedio")] = dc.WeightedIO
	}
	if _, ok := me.value["disk_iorate"]; !ok {
		me.value["disk_iorate"] = make(map[string]float64)
	}
	me.value["disk_iorate"] = CounterMinus(me.diskLastValues, diskNowValues)
	me.diskLastValues = diskNowValues
	return nil
}

var TCP_STATUS = map[string]string{
	"01": "established",
	"02": "syn_sent",
	"03": "syn_recv",
	"04": "fin_wait1",
	"05": "fin_wait2",
	"06": "time_wait",
	"07": "close",
	"08": "close_wait",
	"09": "last_ack",
	"0A": "listen",
	"0B": "closing",    /* Now a valid state */
	"0C": "max_states", /* Leave at the end! */
}

func (me *MetrucExportor) getTcpStatus() error {
	b, err := ioutil.ReadFile("/proc/net/tcp")
	if err != nil {
		return err
	}
	if _, ok := me.value["tcp_conn"]; !ok {
		me.value["tcp_conn"] = make(map[string]float64)
	}
	for k, v := range ParseTcpCount(b) {
		me.value["tcp_conn"][fmt.Sprintf(`status="$s"`, k)] = float64(v)
	}
	return nil
}

func (me *MetrucExportor) getDiskUsage() error {
	d, err := disk.Partitions(false)
	if err != nil {
		return err
	}
	if _, ok := me.value["disk_usage"]; !ok {
		me.value["disk_usage"] = make(map[string]float64)
	}
	for _, v := range d {
		u, err := disk.Usage(v.Mountpoint)
		if err != nil {
			return err
		}
		me.value["disk_usage"] [fmt.Sprintf(`path="%s",m="%s"`, v.Mountpoint, "space")] = u.UsedPercent
		me.value["disk_usage"] [fmt.Sprintf(`path="%s",m="%s"`, v.Mountpoint, "inode")] = u.InodesUsedPercent
	}
	return nil
}

const TOATL = "total"

func PercentMinus(last, now map[string]float64) map[string]float64 {
	lt, ok1 := last[TOATL]
	nt, ok2 := now[TOATL]
	ret := make(map[string]float64)
	if ok1 && ok2 {
		total := float64(nt - lt)
		for k, v := range now {
			if k == TOATL {
				continue
			}
			if lastv, ok := last[k]; ok {
				ret[k] = float64(v-lastv) / total
			}
		}
	}
	return ret
}

func (me *MetrucExportor) getCPU() error {
	v, err := cpu.Times(false)
	if err != nil || len(v) != 1 {
		return err
	}
	cpuNowValues := make(map[string]float64)
	cpuNowValues["user"] = v[0].User
	cpuNowValues["system"] = v[0].System
	cpuNowValues["idle"] = v[0].Idle
	cpuNowValues["iowait"] = v[0].Iowait
	cpuNowValues["irq"] = v[0].Irq
	cpuNowValues["nice"] = v[0].Nice
	cpuNowValues["softirq"] = v[0].Softirq
	cpuNowValues["steal"] = v[0].Steal
	cpuNowValues["guest"] = v[0].Guest
	cpuNowValues["guestNice"] = v[0].GuestNice
	cpuNowValues["stolen"] = v[0].Stolen
	cpuNowValues["total"] = v[0].User + v[0].System + v[0].Nice + v[0].Iowait + v[0].Irq + v[0].Softirq + v[0].Steal + v[0].Guest + v[0].GuestNice + v[0].Stolen + v[0].Idle
	if _, ok := me.value["cpu"]; !ok {
		me.value["cpu"] = make(map[string]float64)
	}
	for k, v := range PercentMinus(me.cpuLastValues, cpuNowValues) {
		me.value["cpu"][fmt.Sprintf(`m="%s"`, k)] = v
	}
	me.cpuLastValues = cpuNowValues
	return nil
}
